package au.com.zonski.gwlite.j2me.client;

import java.util.Hashtable;

import net.jscience.math.kvm.MathFP;

public class WormController implements Controller 
{
	private static final int MAX_DINDEX = 4;
	private static final int DANGLE = MathFP.PI/64;
	private static final int[][] ANGLES = new int[MathFP.toInt(MathFP.div( MathFP.PI*2, DANGLE ))][3]; 
	
	static
	{
		int angle = 0;
		int numAngles = ANGLES.length;
		for( int i=0; i<numAngles; i++ )
		{
			ANGLES[ i ][ 0 ] = angle;
			ANGLES[ i ][ 1 ] = MathFP.sin( angle );
			ANGLES[ i ][ 2 ] = MathFP.cos( angle );
			angle += DANGLE;
		}
	}
	
	private int speed;
	private int maxTurn;
	private int modulo;
	
	public int segments = 10;
	
	private Hashtable previousMoves;
	private int nextId;
	
	public WormController(int speed, int maxTurn, int modulo)
	{
		this.speed = speed;
		this.maxTurn = maxTurn;
		this.modulo = modulo;
		this.previousMoves = new Hashtable();
	}
	
	public void reset() 
	{
		this.previousMoves.clear();
	}

	public boolean update(GWLiteCanvas gameState, GWEntity o) 
	{
		boolean die;
		int[] controllerData = (int[])o.controllerData;
		switch( o.type )
		{
		default:
		case GWEntity.TYPE_WORM:
			{
				if( controllerData == null || controllerData.length != 6)
				{
					int id = this.nextId ++;
					int[][] previousMoves = new int[this.segments * modulo + 1][2];
					this.previousMoves.put( new Integer(id), previousMoves );
					int angle;
					int index;
					if( controllerData != null )
					{
						angle = controllerData[0];
						// find the closest angle
						index = findIndex( angle );
					}
					else
					{
						index = Math.abs( gameState.random.nextInt() % ANGLES.length );
						angle = gameState.random.nextInt() % (MathFP.PI * 2);
					}
					controllerData = new int[]{ 
							angle, 
							0, 
							this.segments, 
							id,
							//gameState.random.nextInt() % MAX_DINDEX,
							0,
							index};
					o.controllerData = controllerData;
				}
				int dindex = controllerData[4];
				//int mod = ((MAX_DINDEX - Math.abs( dindex ))*4)/MAX_DINDEX + 1;
				int mod = (MAX_DINDEX - Math.abs( dindex ) + 1) * 5;
				if( (gameState.updateNumber % mod) == 0 )
				{
					dindex = Math.max( -MAX_DINDEX, Math.min( MAX_DINDEX, dindex  + gameState.random.nextInt() % maxTurn ) );
				}
				int index = ( controllerData[5] + dindex ) % ANGLES.length;
				if( index < 0 )
				{
					index = ANGLES.length + index;
				}
				int[] data = ANGLES[index];
				int angle = data[0];
				int x = o.x;
				int y = o.y;
				int dy = data[1] * speed;
				int dx = data[2] * speed;
				o.x += dx;
				o.y += dy;
				int collision = gameState.enforceBounds( o );
				if( (collision & (GWLiteCanvas.WALL_EAST | GWLiteCanvas.WALL_WEST )  ) != 0 )
				{
					// flip the angle on the y axis
					angle = MathFP.PI - angle;
					index = findIndex( angle );
					//dindex = 0;
				}
				if( (collision & (GWLiteCanvas.WALL_NORTH | GWLiteCanvas.WALL_SOUTH )  ) != 0 )
				{
					angle = -angle;
					index = findIndex( angle );
					//dindex = 0;
				}
				// store the current coordinates -- circular buffer
				int id = controllerData[3];
				Integer key = new Integer(id);
				int[][] previousMoves = (int[][]) this.previousMoves.get( key );
				if( previousMoves != null )
				{
					if( this.segments > 0 )
					{
						int length = previousMoves.length;
						int i = previousMoves[ length - 1 ][0];
						previousMoves[ i ][0] = x;
						previousMoves[ i ][1] = y;
						i --;
						if( i < 0 )
						{
							i = length - 2;
						}
						previousMoves[ length - 1 ][0] = i;
						
						// drop a tail element
						int tailLength = controllerData[2];
						if( tailLength > 0 )
						{
							int modulo = controllerData[1];
							modulo ++ ;
							if( modulo % this.modulo == 0 )
							{
								GWEntity tail = new GWEntity(
										gameState,
										x + o.width/2, y + o.height/2, 
										GWEntity.TYPE_WORM_TAIL 
								);
								tail.controllerData = new int[]{
										this.segments - tailLength, 0, 0, 0, id
								};
								gameState.layers[ GWEntity.TYPE_WORM_TAIL ].addElement( tail );
								tailLength --;
								controllerData[ 2 ] = tailLength;
							}
							controllerData[ 1 ] = modulo;
						}
					}
					controllerData[ 0 ] = angle;
					controllerData[ 4 ] = dindex;
					controllerData[ 5 ] = index;
					die = false;
				}
				else
				{
					die = true;
				}
				break;
					
			}
			case GWEntity.TYPE_WORM_TAIL:
			{
				// the controller data should never be null
				int id = controllerData[4];
				int frame = controllerData[0];
				Integer key = new Integer( id );
				int[][] previousMoves = (int[][])this.previousMoves.get( key );
				die = previousMoves == null; 
				if( die )
				{
					gameState.explode( o );
				}
				else
				{
					int length = previousMoves.length;
					int index = previousMoves[ length - 1 ][0];
					int moveIndex = ( index + frame * this.speed + this.speed-1  ) % (length-1);
					int x = previousMoves[ moveIndex ][0];
					int y = previousMoves[ moveIndex ][1];
					o.x = x;
					o.y = y;
				}
				break;
			}
		}
		return die;
	}

	public void learn(GWLiteCanvas gameState) 
	{
		// do nothing
	}

	public boolean collision(GWLiteCanvas gameState, GWEntity controlled, GWEntity with) 
	{
		
		boolean die;
		switch( controlled.type )
		{
		case GWEntity.TYPE_WORM_TAIL:
			die = false;
			break;
		case GWEntity.TYPE_INTERCEPTOR:
			// if the thing it crashed into is a bullet and hit the nose of
			// the interceptor, we're OK
			if( with.type == GWEntity.TYPE_BULLET )
			{
				int[] controllerData = (int[]) controlled.controllerData;
				int[] withControllerData = (int[]) with.controllerData;
				int rotation = controllerData[0] % (MathFP.PI*2);
				int withRotation = withControllerData[0] % (MathFP.PI*2);
				if( rotation < withRotation )
				{
					rotation += MathFP.PI * 2;
				}
				int diff = rotation - withRotation;
				die = !( diff > (MathFP.PI * 3)/4 && diff < (MathFP.PI * 5)/4 );
			}
			else
			{
				die = true;
			}
			break;
		default:
			die = true;
			break;
		}
		if( die )
		{
			gameState.explode( controlled, with.type == GWEntity.TYPE_BULLET );
			int[] controllerData = (int[])controlled.controllerData;
			if( controllerData != null )
			{
				int id = controllerData[3];
				this.previousMoves.remove( new Integer(id) );
			}
		}
		else
		{
			gameState.explode( with );
		}
		return die;
	}
	
	private static final int findIndex( int angle )
	{
		while( angle < 0 )
		{
			angle += MathFP.PI * 2;
		}
		angle = angle % (MathFP.PI * 2); 
		int index = 0;
		int diff = Integer.MAX_VALUE;
		for( int i = ANGLES.length; i>0; )
		{
			i--;
			int a = ANGLES[i][0];
			int d = Math.abs( a - angle );
			if( d < diff )
			{
				index = i;
				diff = d;
			}
		}
		return index;
	}

}
